In this notebook is presented an analysis of an hike and how it's possible to reconstruct its routes, trajectories and stops. And, more interestingly, the motivations behind those stops.
This part is common for all notebooks, for simplicity. It takes all the Strava activities collected and stores them in lists, dataframes, geodataframes and trajectories for every type of activity (runs, hikes, rides and all).
# to avoid geopandas warnings, don't run if you don't mind warnings
import warnings
warnings.filterwarnings('ignore')
import gpxpy
import os
from tqdm import tqdm
import movingpandas as mpd
import utils
from datetime import timedelta
import pyrosm
activities = []
runs = []
hikes = []
rides = []
for file in tqdm(os.listdir("data/strava_activities/")):
gpx_file = open("data/strava_activities/{}".format(file), 'r')
gpx = gpxpy.parse(gpx_file)
if((gpx.tracks[0].type == 'Run') | (gpx.tracks[0].type == 'running') ):
runs.append(gpx)
activities.append(gpx)
elif(gpx.tracks[0].type == 'Hike'):
hikes.append(gpx)
activities.append(gpx)
elif(gpx.tracks[0].type == 'Ride'):
rides.append(gpx)
activities.append(gpx)
100%|██████████| 159/159 [00:13<00:00, 12.04it/s]
# list of dataframes of all the activities
activities_dfList = utils.toList(activities)
runs_dfList = utils.toList(runs)
rides_dfList = utils.toList(rides)
hikes_dfList = utils.toList(hikes)
100%|██████████| 152/152 [00:02<00:00, 52.06it/s] 100%|██████████| 135/135 [00:01<00:00, 73.75it/s] 100%|██████████| 3/3 [00:00<00:00, 17.25it/s] 100%|██████████| 14/14 [00:00<00:00, 22.06it/s]
# Set time as index for movingpandas
for i in range(len(activities_dfList)):
activities_dfList[i].set_index('time', drop=True, inplace=True)
for i in range(len(runs_dfList)):
runs_dfList[i].set_index('time', drop=True, inplace=True)
for i in range(len(rides_dfList)):
rides_dfList[i].set_index('time', drop=True, inplace=True)
for i in range(len(hikes_dfList)):
hikes_dfList[i].set_index('time', drop=True, inplace=True)
# List of geodataframes of activities
geo_dfList = utils.toGdfList(activities_dfList)
runs_geo_dfList = utils.toGdfList(runs_dfList)
rides_geo_dfList = utils.toGdfList(rides_dfList)
hikes_geo_dfList = utils.toGdfList(hikes_dfList)
100%|██████████| 152/152 [00:03<00:00, 47.04it/s] 100%|██████████| 135/135 [00:02<00:00, 53.34it/s] 100%|██████████| 3/3 [00:00<00:00, 40.72it/s] 100%|██████████| 14/14 [00:00<00:00, 46.85it/s]
# List of all trajectories of the dataset
trajectories = utils.getTrajList(geo_dfList)
runsTrajectories = utils.getTrajList(runs_geo_dfList)
ridesTrajectories = utils.getTrajList(rides_geo_dfList)
hikesTrajectories = utils.getTrajList(hikes_geo_dfList)
Hiking activities could be interesting to analyze. For example, given that I'm not a good biker (or a good trail runner...yet), activities with an high elevation difference between highest and lowest points should be hikes.
utils.getTopElevationDifference(activities,5)
1° highest elevation difference: 1489.0, activity n° 15, type: Hike 2° highest elevation difference: 1368.6, activity n° 7, type: Hike 3° highest elevation difference: 1266.0, activity n° 121, type: Hike 4° highest elevation difference: 969.6, activity n° 115, type: Hike 5° highest elevation difference: 961.6, activity n° 84, type: Hike
trajectories[115].hvplot(c='elevation', geo=True, tiles='OSM', cmap='Blues', line_width=5, colorbar=True)
For example, activity n°115 has been a hike to the Vigolana Shelter (Madonnina) made last summer. Did I took any stop on my way up? Was them for any specific reason?
vigolana = trajectories[115]
stopDetector = mpd.TrajectoryStopDetector(vigolana)
stop_durations = stopDetector.get_stop_time_ranges(min_duration=timedelta(seconds=180), max_diameter=30)
stop_points = stopDetector.get_stop_points(min_duration=timedelta(seconds=180), max_diameter=30)
for i in stop_durations:
print(i)
Traj 115: 2021-07-21 16:12:34 - 2021-07-21 16:15:40 (duration: 0 days 00:03:06) Traj 115: 2021-07-21 16:23:13 - 2021-07-21 16:27:03 (duration: 0 days 00:03:50) Traj 115: 2021-07-21 16:33:47 - 2021-07-21 16:37:31 (duration: 0 days 00:03:44) Traj 115: 2021-07-21 16:38:45 - 2021-07-21 16:44:36 (duration: 0 days 00:05:51) Traj 115: 2021-07-21 16:51:21 - 2021-07-21 16:59:48 (duration: 0 days 00:08:27) Traj 115: 2021-07-21 17:14:49 - 2021-07-21 17:20:11 (duration: 0 days 00:05:22) Traj 115: 2021-07-21 17:24:14 - 2021-07-21 17:29:49 (duration: 0 days 00:05:35) Traj 115: 2021-07-21 17:38:10 - 2021-07-21 17:42:09 (duration: 0 days 00:03:59)
# Only eight stops in a short range for more than 3 minutes. Where were them?
stop_points
| geometry | start_time | end_time | traj_id | duration_s | |
|---|---|---|---|---|---|
| stop_id | |||||
| 115_2021-07-21 16:12:34 | POINT Z (11.19811 45.97597 1261.80000) | 2021-07-21 16:12:34 | 2021-07-21 16:15:40 | 115 | 186.0 |
| 115_2021-07-21 16:23:13 | POINT Z (11.19738 45.97392 1379.00000) | 2021-07-21 16:23:13 | 2021-07-21 16:27:03 | 115 | 230.0 |
| 115_2021-07-21 16:33:47 | POINT Z (11.19543 45.97252 1459.40000) | 2021-07-21 16:33:47 | 2021-07-21 16:37:31 | 115 | 224.0 |
| 115_2021-07-21 16:38:45 | POINT Z (11.19555 45.97222 1469.80000) | 2021-07-21 16:38:45 | 2021-07-21 16:44:36 | 115 | 351.0 |
| 115_2021-07-21 16:51:21 | POINT Z (11.19424 45.97240 1539.20000) | 2021-07-21 16:51:21 | 2021-07-21 16:59:48 | 115 | 507.0 |
| 115_2021-07-21 17:14:49 | POINT Z (11.18993 45.97007 1724.00000) | 2021-07-21 17:14:49 | 2021-07-21 17:20:11 | 115 | 322.0 |
| 115_2021-07-21 17:24:14 | POINT Z (11.18868 45.96932 1788.80000) | 2021-07-21 17:24:14 | 2021-07-21 17:29:49 | 115 | 335.0 |
| 115_2021-07-21 17:38:10 | POINT Z (11.18717 45.96960 1868.60000) | 2021-07-21 17:38:10 | 2021-07-21 17:42:09 | 115 | 239.0 |
Let's see where these stops have been taken along the hike.
vigoPlot = vigolana.hvplot(title='Vigolana Hike', line_width=3, tiles='StamenTerrain', cmap='Blues', c='elevation', colorbar=True)
stopPlot = vigoPlot * stop_points.hvplot(geo=True, size='duration_s', color='red')
stopPlot
A possible cause for the stops could be the high elevation gain made in a short time. Let's check if it's the case.
stop_points = utils.getStopElevationDiff(stop_points, vigolana).set_crs(epsg=4326)
stop_points
| geometry | start_time | end_time | traj_id | duration_s | elevation_diff | time_diff | |
|---|---|---|---|---|---|---|---|
| stop_id | |||||||
| 115_2021-07-21 16:12:34 | POINT Z (11.19811 45.97597 1261.80000) | 2021-07-21 16:12:34 | 2021-07-21 16:15:40 | 115 | 186.0 | 231.0 | 0 days 00:20:17 |
| 115_2021-07-21 16:23:13 | POINT Z (11.19738 45.97392 1379.00000) | 2021-07-21 16:23:13 | 2021-07-21 16:27:03 | 115 | 230.0 | 117.2 | 0 days 00:07:33 |
| 115_2021-07-21 16:33:47 | POINT Z (11.19543 45.97252 1459.40000) | 2021-07-21 16:33:47 | 2021-07-21 16:37:31 | 115 | 224.0 | 80.4 | 0 days 00:06:44 |
| 115_2021-07-21 16:38:45 | POINT Z (11.19555 45.97222 1469.80000) | 2021-07-21 16:38:45 | 2021-07-21 16:44:36 | 115 | 351.0 | 10.4 | 0 days 00:01:14 |
| 115_2021-07-21 16:51:21 | POINT Z (11.19424 45.97240 1539.20000) | 2021-07-21 16:51:21 | 2021-07-21 16:59:48 | 115 | 507.0 | 69.4 | 0 days 00:06:45 |
| 115_2021-07-21 17:14:49 | POINT Z (11.18993 45.97007 1724.00000) | 2021-07-21 17:14:49 | 2021-07-21 17:20:11 | 115 | 322.0 | 184.8 | 0 days 00:15:01 |
| 115_2021-07-21 17:24:14 | POINT Z (11.18868 45.96932 1788.80000) | 2021-07-21 17:24:14 | 2021-07-21 17:29:49 | 115 | 335.0 | 64.8 | 0 days 00:04:03 |
| 115_2021-07-21 17:38:10 | POINT Z (11.18717 45.96960 1868.60000) | 2021-07-21 17:38:10 | 2021-07-21 17:42:09 | 115 | 239.0 | 79.8 | 0 days 00:08:21 |
In some cases stops could explained by elevation gain in a short amount of time, but in other there should be different reasons. A good viewpoint to take pictures while resting, maybe? Let's check with the longest stop I took.
longestStop = stop_points[stop_points.duration_s==max(stop_points.duration_s)]
bbox = vigolana.get_mcp()
# All point of interests in the minimum convex polygon containing the hike
hikeOSM = pyrosm.OSM("./data/altopiano_della_vigolana.osm.pbf", bounding_box=bbox)
hikePois = hikeOSM.get_pois()
hikePois
| lon | timestamp | changeset | tags | id | lat | version | name | operator | ref | drinking_water | bicycle | information | tourism | geometry | osm_type | amenity | building | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 11.194252 | 1594922072 | 0.0 | {"direction":"N"} | 910787221 | 45.972450 | 3 | Polsa | None | None | None | None | None | viewpoint | POINT (11.19425 45.97245) | node | NaN | NaN |
| 1 | 11.185942 | 1572786755 | 0.0 | {"ele":"2030","fee":"yes","capacity":"6"} | 914756310 | 45.971054 | 6 | Bivacco Vigolana | Società Alpinisti Tridentini | None | no | None | None | wilderness_hut | POINT (11.18594 45.97105) | node | NaN | NaN |
| 2 | 11.198959 | 1340702685 | 0.0 | {"mtb":"yes","horse":"yes","hiking":"yes"} | 1802521175 | 45.976658 | 1 | None | None | None | None | no | guidepost | information | POINT (11.19896 45.97666) | node | NaN | NaN |
| 3 | 11.201583 | 1340702687 | 0.0 | {"mtb":"yes","horse":"yes","hiking":"yes"} | 1802521237 | 45.979012 | 1 | None | None | None | None | no | guidepost | information | POINT (11.20158 45.97901) | node | NaN | NaN |
| 4 | 11.203117 | 1340702689 | 0.0 | {"mtb":"yes","horse":"yes","hiking":"yes"} | 1802521309 | 45.980946 | 1 | None | None | None | None | no | guidepost | information | POINT (11.20312 45.98095) | node | NaN | NaN |
| 5 | 11.203637 | 1340702689 | 0.0 | {"mtb":"yes","horse":"yes","hiking":"yes"} | 1802521315 | 45.982376 | 1 | None | None | None | None | no | guidepost | information | POINT (11.20364 45.98238) | node | NaN | NaN |
| 6 | 11.185840 | 1615634668 | 0.0 | {"hiking":"yes","wikimedia_commons":"File:Segn... | 4437914625 | 45.969315 | 4 | None | None | 425-444 | None | None | guidepost | information | POINT (11.18584 45.96931) | node | NaN | NaN |
| 7 | NaN | 1532186863 | NaN | NaN | 152409892 | NaN | 2 | Bivacco Madonnina | NaN | NaN | NaN | NaN | NaN | NaN | POLYGON ((11.18591 45.97107, 11.18596 45.97107... | way | shelter | yes |
for i in range(len(hikePois)):
if hikePois.to_crs('epsg:32632').geometry.values[i].buffer(10).contains(longestStop.to_crs('epsg:32632').geometry.values[0]):
print("Name: {}; tourism: {}".format(hikePois.at[i, 'name'], hikePois.at[i, 'tourism']))
Name: Polsa; tourism: viewpoint
Apparently, I stopped for a short break, but also to take a nice picture and enjoy the view. Let's confirm it in the map seen before, but changing the layer with a more appropriate one.
vigoPlot = vigolana.hvplot(title='Vigolana Hike', line_width=3, tiles='OSM', cmap='Blues', c='elevation', colorbar=True)
stopPlot = vigoPlot * longestStop.hvplot(geo=True, size='duration_s', color='red')
stopPlot
As expected, with the OSM layer we can see that the longest stop has been taken in proximity (within 10m) of the viewpoint Polsa.
Fun fact: this viewpoint has been mapped last time in 2020 by the user Martin Larcher (https://www.openstreetmap.org/node/910787221); at the end of the hike we just analyzed, I spent the night in the Madonnina shelter with...Martin Larcher himself, along with his family!